<?php

declare(strict_types=1);

/*
 * This file is part of Sulu.
 *
 * (c) Sulu GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Snippet\Infrastructure\Symfony\HttpKernel;

use Sulu\Bundle\HttpCacheBundle\ReferenceStore\ReferenceStore;
use Sulu\Bundle\PersistenceBundle\DependencyInjection\PersistenceExtensionTrait;
use Sulu\Bundle\PersistenceBundle\PersistenceBundleTrait;
use Sulu\Snippet\Application\Mapper\SnippetContentMapper;
use Sulu\Snippet\Application\Mapper\SnippetMapperInterface;
use Sulu\Snippet\Application\MessageHandler\ApplyWorkflowTransitionSnippetMessageHandler;
use Sulu\Snippet\Application\MessageHandler\CopyLocaleSnippetMessageHandler;
use Sulu\Snippet\Application\MessageHandler\CreateSnippetMessageHandler;
use Sulu\Snippet\Application\MessageHandler\ModifySnippetAreaMessageHandler;
use Sulu\Snippet\Application\MessageHandler\ModifySnippetMessageHandler;
use Sulu\Snippet\Application\MessageHandler\RemoveSnippetAreaMessageHandler;
use Sulu\Snippet\Application\MessageHandler\RemoveSnippetMessageHandler;
use Sulu\Snippet\Application\MessageHandler\RemoveSnippetTranslationMessageHandler;
use Sulu\Snippet\Application\MessageHandler\RestoreSnippetVersionMessageHandler;
use Sulu\Snippet\Domain\Event\SnippetCreatedEvent;
use Sulu\Snippet\Domain\Event\SnippetModifiedEvent;
use Sulu\Snippet\Domain\Event\SnippetRemovedEvent;
use Sulu\Snippet\Domain\Event\SnippetRestoredEvent;
use Sulu\Snippet\Domain\Event\SnippetTranslationAddedEvent;
use Sulu\Snippet\Domain\Event\SnippetTranslationRemovedEvent;
use Sulu\Snippet\Domain\Event\SnippetTranslationRestoredEvent;
use Sulu\Snippet\Domain\Model\Snippet;
use Sulu\Snippet\Domain\Model\SnippetArea;
use Sulu\Snippet\Domain\Model\SnippetAreaInterface;
use Sulu\Snippet\Domain\Model\SnippetDimensionContent;
use Sulu\Snippet\Domain\Model\SnippetDimensionContentInterface;
use Sulu\Snippet\Domain\Model\SnippetInterface;
use Sulu\Snippet\Domain\Repository\SnippetAreaRepositoryInterface;
use Sulu\Snippet\Domain\Repository\SnippetRepositoryInterface;
use Sulu\Snippet\Infrastructure\Doctrine\Repository\SnippetAreaRepository;
use Sulu\Snippet\Infrastructure\Doctrine\Repository\SnippetRepository;
use Sulu\Snippet\Infrastructure\Sulu\Admin\SnippetAdmin;
use Sulu\Snippet\Infrastructure\Sulu\Admin\SnippetAreaAdmin;
use Sulu\Snippet\Infrastructure\Sulu\Content\PropertyResolver\SingleSnippetSelectionPropertyResolver;
use Sulu\Snippet\Infrastructure\Sulu\Content\PropertyResolver\SnippetSelectionPropertyResolver;
use Sulu\Snippet\Infrastructure\Sulu\Content\ResourceLoader\SnippetResourceLoader;
use Sulu\Snippet\Infrastructure\Sulu\Content\SmartResolver\SnippetAreaSmartResolver;
use Sulu\Snippet\Infrastructure\Sulu\Content\SnippetSmartContentProvider;
use Sulu\Snippet\Infrastructure\Sulu\HttpCache\EventSubscriber\SnippetAreaCacheInvalidationSubscriber;
use Sulu\Snippet\Infrastructure\Sulu\HttpCache\EventSubscriber\SnippetCacheInvalidationSubscriber;
use Sulu\Snippet\Infrastructure\Sulu\Reference\SnippetReferenceRefresher;
use Sulu\Snippet\Infrastructure\Sulu\Search\AdminSnippetIndexListener;
use Sulu\Snippet\Infrastructure\Sulu\Search\AdminSnippetReindexProvider;
use Sulu\Snippet\Infrastructure\Sulu\Trash\SnippetTrashItemHandler;
use Sulu\Snippet\Infrastructure\Symfony\CompilerPass\SnippetAreaCompilerPass;
use Sulu\Snippet\Infrastructure\Symfony\Normalizer\SnippetAreaNormalizer;
use Sulu\Snippet\Infrastructure\Symfony\Twig\SnippetAreaTwigExtension;
use Sulu\Snippet\UserInterface\Controller\Admin\SnippetAreaController;
use Sulu\Snippet\UserInterface\Controller\Admin\SnippetController;
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

use function Symfony\Component\DependencyInjection\Loader\Configurator\param;
use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator;

use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

/**
 * @codeCoverageIgnore
 */
final class SuluSnippetBundle extends AbstractBundle
{
    use PersistenceBundleTrait;
    use PersistenceExtensionTrait;

    /**
     * @internal this method is not part of the public API and should only be called by the Symfony framework classes
     */
    public function configure(DefinitionConfigurator $definition): void
    {
        $definition->rootNode() // @phpstan-ignore-line
            ->children()
                ->arrayNode('objects')
                    ->addDefaultsIfNotSet()
                    ->children()
                        ->arrayNode('snippet')
                            ->addDefaultsIfNotSet()
                            ->children()
                                ->scalarNode('model')->defaultValue(Snippet::class)->end()
                            ->end()
                        ->end()
                        ->arrayNode('snippet_content')
                            ->addDefaultsIfNotSet()
                            ->children()
                                ->scalarNode('model')->defaultValue(SnippetDimensionContent::class)->end()
                            ->end()
                        ->end()
                        ->arrayNode('snippet_area')
                            ->addDefaultsIfNotSet()
                            ->children()
                                ->scalarNode('model')->defaultValue(SnippetArea::class)->end()
                            ->end()
                        ->end()
                    ->end()
                ->end()
            ->end();
    }

    /**
     * @param array<string, mixed> $config
     *
     * @internal this method is not part of the public API and should only be called by the Symfony framework classes
     */
    public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
    {
        $this->configurePersistence($config['objects'], $builder); // @phpstan-ignore-line

        $services = $container->services();

        // Define autoconfigure interfaces for mappers
        $builder->registerForAutoconfiguration(SnippetMapperInterface::class)
            ->addTag('sulu_snippet.snippet_mapper');

        // Message Handler services
        $services->set('sulu_snippet.create_snippet_handler')
            ->class(CreateSnippetMessageHandler::class)
            ->args([
                new Reference('sulu_snippet.snippet_repository'),
                tagged_iterator('sulu_snippet.snippet_mapper'),
                new Reference('sulu_activity.domain_event_collector'),
            ])
            ->tag('messenger.message_handler');

        $services->set('sulu_snippet.modify_snippet_handler')
            ->class(ModifySnippetMessageHandler::class)
            ->args([
                new Reference('sulu_snippet.snippet_repository'),
                tagged_iterator('sulu_snippet.snippet_mapper'),
                new Reference('sulu_activity.domain_event_collector'),
            ])
            ->tag('messenger.message_handler');

        $services->set('sulu_snippet.remove_snippet_handler')
            ->class(RemoveSnippetMessageHandler::class)
            ->args([
                new Reference('sulu_snippet.snippet_repository'),
                new Reference('sulu_activity.domain_event_collector'),
                new Reference('sulu_trash.trash_manager', ContainerInterface::NULL_ON_INVALID_REFERENCE),
            ])
            ->tag('messenger.message_handler');

        $services->set('sulu_snippet.remove_snippet_translation_handler')
            ->class(RemoveSnippetTranslationMessageHandler::class)
            ->args([
                new Reference('sulu_snippet.snippet_repository'),
                new Reference('sulu_activity.domain_event_collector'),
                new Reference('sulu_trash.trash_manager', ContainerInterface::NULL_ON_INVALID_REFERENCE),
            ])
            ->tag('messenger.message_handler');

        $services->set('sulu_snippet.apply_workflow_transition_snippet_handler')
            ->class(ApplyWorkflowTransitionSnippetMessageHandler::class)
            ->args([
                new Reference('sulu_snippet.snippet_repository'),
                new Reference('sulu_content.content_workflow'),
                new Reference('sulu_activity.domain_event_collector'),
            ])
            ->tag('messenger.message_handler');

        $services->set('sulu_snippet.copy_locale_snippet_handler')
            ->class(CopyLocaleSnippetMessageHandler::class)
            ->args([
                new Reference('sulu_snippet.snippet_repository'),
                new Reference('sulu_content.content_copier'),
                new Reference('sulu_activity.domain_event_collector'),
            ])
            ->tag('messenger.message_handler');

        $services->set('sulu_snippet.restore_snippet_version_handler')
            ->class(RestoreSnippetVersionMessageHandler::class)
            ->args([
                new Reference('sulu_snippet.snippet_repository'),
                new Reference('sulu_content.content_copier'),
                new Reference('sulu_activity.domain_event_collector'),
            ])
            ->tag('messenger.message_handler');

        // Snippet area
        $services->set('sulu_snippet.modify_snippet_area_handler')
            ->class(ModifySnippetAreaMessageHandler::class)
            ->args([
                new Reference(SnippetAreaRepositoryInterface::class),
                new Reference(SnippetRepositoryInterface::class),
                new Reference('sulu_activity.domain_event_collector'),
            ])
            ->tag('messenger.message_handler');

        $services->set('sulu_snippet.remove_snippet_area_handler')
            ->class(RemoveSnippetAreaMessageHandler::class)
            ->args([
                new Reference('sulu_snippet.snippet_area_repository'),
                new Reference('sulu_activity.domain_event_collector'),
            ])
            ->tag('messenger.message_handler');

        // Mapper service
        $services->set('sulu_snippet.snippet_content_mapper')
            ->class(SnippetContentMapper::class)
            ->args([
                new Reference('sulu_content.content_persister'),
            ])
            ->tag('sulu_snippet.snippet_mapper');

        // Sulu Integration service
        $services->set('sulu_snippet.snippet_admin')
            ->class(SnippetAdmin::class)
            ->args([
                new Reference('sulu_admin.view_builder_factory'),
                new Reference('sulu_content.content_view_builder_factory'),
                new Reference('sulu_security.security_checker'),
                new Reference('sulu.core.localization_manager'),
                new Reference('sulu_activity.activity_list_view_builder_factory'),
            ])
            ->tag('sulu.context', ['context' => 'admin'])
            ->tag('sulu.admin');

        $services->set('sulu_snippet.snippet_area_admin')
            ->class(SnippetAreaAdmin::class)
            ->args([
                new Reference('sulu_admin.view_builder_factory'),
                new Reference('sulu_security.security_checker'),
            ])
            ->tag('sulu.context', ['context' => 'admin'])
            ->tag('sulu.admin');

        $services->set('sulu_snippet.snippet_area_normalizer', SnippetAreaNormalizer::class)
            ->args([
                new Reference('serializer.normalizer.object'),
                param(SnippetAreaCompilerPass::SNIPPET_AREA_PARAM),
            ])
            ->tag('serializer.normalizer');

        // Twig services
        $services->set('sulu_snippet.snippet_area_twig_extension')
            ->class(SnippetAreaTwigExtension::class)
            ->args([
                new Reference('sulu_snippet.snippet_area_repository'),
                new Reference('sulu_content.content_aggregator'),
                new Reference('sulu_core.webspace.request_analyzer'),
                new Reference('sulu_snippet.snippet_reference_store'),
                new Reference('sulu_content.content_resolver'),
            ])
            ->tag('twig.extension');

        // Repositories services
        $services->set('sulu_snippet.snippet_repository')
            ->class(SnippetRepository::class)
            ->args([
                new Reference('doctrine.orm.entity_manager'),
                new Reference('sulu_content.dimension_content_query_enhancer'),
            ]);

        $services->alias(SnippetRepositoryInterface::class, 'sulu_snippet.snippet_repository');

        $services->set('sulu_snippet.snippet_area_repository')
            ->class(SnippetAreaRepository::class)
            ->args([
                new Reference('doctrine.orm.entity_manager'),
            ]);

        $services->alias(SnippetAreaRepositoryInterface::class, 'sulu_snippet.snippet_area_repository');

        // Controllers services
        $services->set('sulu_snippet.admin_snippet_controller')
            ->class(SnippetController::class)
            ->public()
            ->args([
                new Reference('sulu_snippet.snippet_repository'),
                new Reference('sulu_message_bus'),
                new Reference('serializer'),
                // additional services to be removed when no longer needed
                new Reference('sulu_content.content_manager'),
                new Reference('sulu_core.list_builder.field_descriptor_factory'),
                new Reference('sulu_core.doctrine_list_builder_factory'),
                new Reference('sulu_core.doctrine_rest_helper'),
                param(SnippetAreaCompilerPass::SNIPPET_AREA_PARAM),
            ])
            ->tag('sulu.context', ['context' => 'admin']);

        $services->set('sulu_snippet.snippet_reference_store')
            ->class(ReferenceStore::class)
            ->tag('sulu_website.reference_store', ['alias' => SnippetInterface::RESOURCE_KEY]);

        $services->set('sulu_snippet.admin_snippet_area_controller')
            ->class(SnippetAreaController::class)
            ->public()
            ->args([
                new Reference('sulu_message_bus'),
                new Reference('serializer'),
                new Reference('sulu_core.list_builder.field_descriptor_factory'),
                new Reference('sulu_core.doctrine_list_builder_factory'),
                new Reference('sulu_core.doctrine_rest_helper'),
                param(SnippetAreaCompilerPass::SNIPPET_AREA_PARAM),
            ])
            ->tag('sulu.context', ['context' => 'admin']);

        // PropertyResolver services
        $services->set('sulu_snippet.single_snippet_selection_property_resolver')
            ->class(SingleSnippetSelectionPropertyResolver::class)
            ->tag('sulu_content.property_resolver');

        $services->set('sulu_snippet.snippet_selection_property_resolver')
            ->class(SnippetSelectionPropertyResolver::class)
            ->tag('sulu_content.property_resolver');

        // SmartResolver services
        $services->set('sulu_snippet.snippet_area_smart_resolver')
            ->class(SnippetAreaSmartResolver::class)
            ->args([
                new Reference('sulu_snippet.snippet_area_repository'),
                new Reference('sulu_core.webspace.request_analyzer'),
            ])
            ->tag('sulu_content.smart_resolver', ['type' => 'snippet_area_default']);

        // ResourceLoader services
        $services->set('sulu_snippet.snippet_resource_loader')
            ->class(SnippetResourceLoader::class)
            ->args([
                new Reference('sulu_snippet.snippet_repository'),
            ])
            ->tag('sulu_content.resource_loader', ['type' => SnippetResourceLoader::RESOURCE_LOADER_KEY]);

        // Content services
        $services->set('sulu_snippet.snippet_reference_store')
            ->class(ReferenceStore::class)
            ->tag('sulu_website.reference_store', ['alias' => SnippetInterface::RESOURCE_KEY]);

        // Smart Content services
        $services->set('sulu_snippet.snippet_smart_content_provider')
            ->class(SnippetSmartContentProvider::class)
            ->args([
                new Reference('sulu_content.dimension_content_query_enhancer'),
                new Reference('sulu_admin.smart_content_query_enhancer'),
                new Reference('doctrine.orm.entity_manager'),
            ])
            ->tag('sulu_content.smart_content_provider', ['type' => SnippetInterface::RESOURCE_KEY]);

        // Reference services
        $services->set('sulu_snippet.snippet_reference_refresher')
            ->class(SnippetReferenceRefresher::class)
            ->args([
                new Reference('doctrine.orm.entity_manager'),
                new Reference('sulu_reference.reference_repository'),
                new Reference('sulu_content.content_view_resolver'),
                new Reference('sulu_content.content_merger'),
            ])
            ->tag('sulu_reference.refresher');

        // Cache Invalidation
        $services->set('sulu_snippet.snippet_cache_invalidation_subscriber')
            ->class(SnippetCacheInvalidationSubscriber::class)
            ->args([
                new Reference('sulu_http_cache.cache_manager', ContainerInterface::NULL_ON_INVALID_REFERENCE),
                new Reference('sulu_content.content_aggregator'),
                param(SnippetAreaCompilerPass::SNIPPET_AREA_PARAM),
            ])
            ->tag('kernel.event_subscriber');

        $services->set('sulu_snippet.snippet_area_cache_invalidation_subscriber')
            ->class(SnippetAreaCacheInvalidationSubscriber::class)
            ->args([
                new Reference('sulu_http_cache.cache_manager', ContainerInterface::NULL_ON_INVALID_REFERENCE),
                param(SnippetAreaCompilerPass::SNIPPET_AREA_PARAM),
            ])
            ->tag('kernel.event_subscriber');

        // Trash services
        /** @var array<string, class-string> $bundles */
        $bundles = $builder->getParameter('kernel.bundles');
        if (isset($bundles['SuluTrashBundle'])) {
            $services->set('sulu_snippet.trash_item_handler')
                ->class(SnippetTrashItemHandler::class)
                ->args([
                    new Reference('sulu_trash.trash_item_repository'),
                    new Reference('sulu_snippet.snippet_repository'),
                    new Reference('sulu_content.content_normalizer'),
                    new Reference('sulu_content.content_merger'),
                    tagged_iterator('sulu_snippet.snippet_mapper'),
                    new Reference('sulu_activity.domain_event_collector'),
                ])
                ->tag('sulu_trash.store_trash_item_handler')
                ->tag('sulu_trash.restore_trash_item_handler')
                ->tag('sulu_trash.restore_configuration_provider');
        }

        $services->set('sulu_snippet.admin_snippet_index_listener')
            ->class(AdminSnippetIndexListener::class)
            ->args([
                new Reference('sulu_message_bus'),
            ])
            ->tag('kernel.event_listener', ['event' => SnippetCreatedEvent::class, 'method' => 'onSnippetChanged'])
            ->tag('kernel.event_listener', ['event' => SnippetModifiedEvent::class, 'method' => 'onSnippetChanged'])
            ->tag('kernel.event_listener', ['event' => SnippetRemovedEvent::class, 'method' => 'onSnippetChanged'])
            ->tag('kernel.event_listener', ['event' => SnippetRestoredEvent::class, 'method' => 'onSnippetChanged'])
            ->tag('kernel.event_listener', ['event' => SnippetTranslationRestoredEvent::class, 'method' => 'onSnippetChanged'])
            ->tag('kernel.event_listener', ['event' => SnippetTranslationAddedEvent::class, 'method' => 'onSnippetChanged'])
            ->tag('kernel.event_listener', ['event' => SnippetTranslationRemovedEvent::class, 'method' => 'onSnippetChanged']);

        $services->set('sulu_snippet.admin_snippet_reindex_provider')
            ->class(AdminSnippetReindexProvider::class)
            ->args([
                new Reference('doctrine.orm.entity_manager'),
            ])
            ->tag('cmsig_seal.reindex_provider');
    }

    /**
     * @internal this method is not part of the public API and should only be called by the Symfony framework classes
     */
    public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void
    {
        if ($builder->hasExtension('sulu_admin')) {
            $builder->prependExtensionConfig(
                'sulu_admin',
                [
                    'lists' => [
                        'directories' => [
                            \dirname(__DIR__, 4) . '/config/lists',
                        ],
                    ],
                    'forms' => [
                        'directories' => [
                            // \dirname(__DIR__, 4) . '/config/forms',
                        ],
                    ],
                    'templates' => [
                        SnippetInterface::TEMPLATE_TYPE => [
                            'default_type' => null,
                            'directories' => [
                                'app' => '%kernel.project_dir%/config/templates/snippets',
                            ],
                        ],
                    ],
                    'resources' => [
                        'snippets' => [
                            'routes' => [
                                'list' => 'sulu_snippet.get_snippets',
                                'detail' => 'sulu_snippet.get_snippet',
                            ],
                        ],
                        'snippets_versions' => [
                            'routes' => [
                                'list' => 'sulu_snippet.get_snippet_versions',
                                'detail' => 'sulu_snippet.get_snippet',
                            ],
                        ],
                        'snippet_areas' => [
                            'routes' => [
                                'detail' => 'sulu_snippet_area.put_snippet_area',
                                'list' => 'sulu_snippet_area.get_snippet_areas',
                            ],
                        ],
                    ],
                    'field_type_options' => [
                        'selection' => [
                            'snippet_selection' => [
                                'default_type' => 'list_overlay',
                                'resource_key' => 'snippets',
                                'types' => [
                                    'list_overlay' => [
                                        'adapter' => 'table',
                                        'list_key' => 'snippets',
                                        'display_properties' => ['title', 'routePath'],
                                        'icon' => 'su-newspaper',
                                        'label' => 'sulu_snippet.selection_label',
                                        'overlay_title' => 'sulu_snippet.selection_overlay_title',
                                    ],
                                ],
                            ],
                        ],
                        'single_selection' => [
                            'single_snippet_selection' => [
                                'default_type' => 'list_overlay',
                                'resource_key' => 'snippets',
                                'types' => [
                                    'list_overlay' => [
                                        'adapter' => 'table',
                                        'list_key' => 'snippets',
                                        'display_properties' => ['title'],
                                        'empty_text' => 'sulu_snippet.no_snippet_selected',
                                        'icon' => 'su-newspaper',
                                        'overlay_title' => 'sulu_snippet.single_selection_overlay_title',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            );
        }

        if ($builder->hasExtension('doctrine')) {
            $builder->prependExtensionConfig(
                'doctrine',
                [
                    'orm' => [
                        'mappings' => [
                            'SuluSnippet' => [
                                'type' => 'xml',
                                'prefix' => 'Sulu\Snippet\Domain\Model',
                                'dir' => \dirname(__DIR__, 4) . '/config/doctrine/Snippet',
                                'alias' => 'SuluSnippet',
                                'is_bundle' => false,
                                'mapping' => true,
                            ],
                        ],
                    ],
                ],
            );
        }

        if ($builder->hasExtension('sulu_search')) {
            $builder->prependExtensionConfig(
                'sulu_search',
                [
                    'admin' => [
                        'resources' => [
                            SnippetInterface::RESOURCE_KEY => [
                                'name' => 'sulu_snippet.snippets',
                                'icon' => 'su-snippet',
                                'route' => [
                                    'name' => 'sulu_snippet.snippet.edit_tabs',
                                    'resultToRoute' => [
                                        'resourceId' => 'id',
                                        'locale' => 'locale',
                                    ],
                                ],
                                'securityContext' => SnippetAdmin::SECURITY_CONTEXT,
                            ],
                        ],
                    ],
                ],
            );
        }
    }

    /**
     * @internal this method is not part of the public API and should only be called by the Symfony framework classes
     */
    public function getPath(): string
    {
        return \dirname(__DIR__, 4); // target the root of the library where config, src, ... is located
    }

    /**
     * @internal this method is not part of the public API and should only be called by the Symfony framework classes
     */
    public function build(ContainerBuilder $container): void
    {
        $this->buildPersistence([
            SnippetInterface::class => 'sulu.model.snippet.class',
            SnippetDimensionContentInterface::class => 'sulu.model.snippet_content.class',
            SnippetAreaInterface::class => 'sulu.model.snippet_area.class',
        ], $container);

        $container->addCompilerPass(new SnippetAreaCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1024);
    }
}
